#include "regclient.h"
#include "Poco/Exception.h"
#include "..\server\command.h"
#include <algorithm>
#include "log.h"
#include "cryptoSocket.h"

using Poco::Exception;

RegClient*      RegClient::s_pRegClient = NULL;

bool RegClient::IsRegistered(const string hwid)
{
    return m_client.GetRegistered();
}

int RegClient::Register(PBasicInformation pInfo)
{
    int    nRet  = Fail;
    if (pInfo == NULL)
    {
        return InvalidDataFormat;
    }

    do 
    {
        CryptoSocket cs(server_ip, server_port);
        // initialize client data
        if(!m_client.Initialize(pInfo))
        {
            FLogger.information("failed to initialize user information");
            nRet = InvalidDataFormat;
            break;
        }
        // make registration data
        ServerCommand sc = {0};

        int nSend = sizeof(ServerCommand);
        if(!m_client.GetRegistrationData(&sc,SC_LEN))
        {
            FLogger.information("failed to get registration data...");
            nRet = Fail;
            break;
        }
        
        if (cs.Send(&sc, SC_LEN) == -1)
        {
            FLogger.information("failed to send data");
            nRet = SocketError;
            break;
        }

        ServerResponse res = {0};
        if (cs.Recv(&res, SR_LEN) == -1)
        {
            FLogger.information("error occurs in receiveBytes");
            nRet = SocketError;
            break;
        }

        if (res.cmd_state != Success)
        {
            FLogger.information("cmd state is not success");
            nRet = res.cmd_state;
            break;
        }

        m_client.UpdateRegistrationInfo(&res);

        nRet = Success;
    } while (false);

    return nRet;
}

bool RegClient::CreateInstance(IRegClient*& pRegClient)
{
    FLogger.information("RegClient::Createinstance called");

    pRegClient = NULL;
    bool bRet = false;

    if(s_pRegClient == NULL)
    {
        s_pRegClient = new RegClient();
        if (s_pRegClient == NULL)
        {
            FLogger.information("failed to create instance");
            bRet = false;
        }
    }

    pRegClient = s_pRegClient;
    return true;
}

void RegClient::run()
{
    while(!m_stop)
    {
        Sleep(30 * 1000);
        if (!m_client.GetRegistered())
        {
            continue;
        }

        int nMsg = Success;

        do 
        {
            if (m_heartbeat_fail_count == 5)
            {
                break;
            }

            nMsg = HeartBeat();
            if (Success == nMsg)
            {
                m_heartbeat_fail_count = 0;
                if (m_client.IsExpired())
                {
                    nMsg = KeyExpired;
                    break;
                }
            }
            else if (nMsg == SocketError || nMsg == SQliteError)
            {
                m_heartbeat_fail_count++;
            }
            else
            {
                break;
            }

        } while (false);

        if (nMsg != Success)
        {
            vector<IRegisterNotification*>::iterator it;
            for (it = s_vNotificationClient.begin(); it != s_vNotificationClient.end(); it++)
            {
                (*it)->OnRegisterNotification(nMsg);
            }
        }
    }
}

//////////////////////////////////////////////////////////////////////////
// IRegisterNotification
//////////////////////////////////////////////////////////////////////////

class FindNotificationClient
{
public:
    FindNotificationClient(IRegisterNotification* l) : m_l(l) {}
    bool operator() (IRegisterNotification* r) {return m_l == r;}
private:
    IRegisterNotification* m_l;
};

bool RegClient::RegisterNotification(IRegisterNotification* pClient )
{
    if (pClient == NULL)
    {
        return false;
    }

    vector<IRegisterNotification*>::iterator it;
    
    it = find_if(s_vNotificationClient.begin(), s_vNotificationClient.end(),FindNotificationClient(pClient));
    if (it == s_vNotificationClient.end())
    {
        // does not exist
        s_vNotificationClient.push_back(pClient);
    }

    return true;
}

bool RegClient::UnRegisterNotification( IRegisterNotification* pClient )
{
    if (pClient == NULL)
    {
        return false;
    }

    vector<IRegisterNotification*>::iterator it;

    it = find_if(s_vNotificationClient.begin(), s_vNotificationClient.end(),FindNotificationClient(pClient));
    if (it != s_vNotificationClient.end())
    {
        // exist and delete 
        s_vNotificationClient.erase(it);
    }

    return true;
}


bool RegClient::Test()
{
    printf("=============================  cmd_test  =======================================\n");
    ServerResponse res = {0};
    int nRecv = 0;
    try
    {
        CryptoSocket cs(server_ip,server_port);


        ServerCommand cmd = {0};
        cmd.cmd_type    = Cmd_Test;
        int n = cs.Send(&cmd, SC_LEN);

        printf("%d bytes sent\n", n);
        nRecv = cs.Recv(&res, SR_LEN);
    }
    catch (Exception& e)
    {
    	printf("%s\n",e.what());
    }

    PProductKeyInfo pInfo = (PProductKeyInfo)res.data;

    printf("%d bytes received. sizeof(CmdResponse) is %d\n", nRecv, sizeof(ServerResponse));
    printf("Command State   : %d\n", res.cmd_state);
    printf("Command Type    : %d\n", res.cmd_type);
    printf("Data Length     : %d\n", res.data_len);
    printf("Hardware ID     : %s\n", pInfo->hardware_id);
    printf("Product Key     : %s\n", pInfo->product_key);
    printf("Generate Time   : %x\n", pInfo->generate_time);
    printf("Register Time   : %x\n", pInfo->register_time);
    printf("Expire Time     : %x\n", pInfo->duration);
    printf("Key State       : %d\n", pInfo->state);
    printf("================================================================================\n");
    
    return true;
}

const string RegClient::ErrorCodeToString(int nErrorcode)
{
    switch (nErrorcode)
    {
    case Success:
        return string("Success");
    case Fail:
        return string("Fail");
    case UserNameAlreadyExist:
        return string("UserNameAlreadyExist");
    case WrongPassword:
        return string("WrongPassword");
    case KeyExpired:
        return string("KeyExpired");
    case HardwareIDChanged:
        return string("HardwareIDChanged");
    case InvalidDataFormat:
        return string("InvalidDataFormat");
    case InvalidProductKey:
        return string("InvalidProductKey");
    case KeyAlreadyRegistered:
        return string("KeyAlreadyRegistered");
    case SocketError:
        return string("SocketError");
    case UserMustBeLoggedIn:
        return string("UserMustBeLoggedIn");
    case SQliteError:
        return string("SQliteError");
    case InexistentUser:
        return string("InexistentUser");
    case WrongEmail:
        return string("WrongEmail");
    case WrongHardwareID:
        return string("WrongHardwareID");
    case NoAvailableKeyBinded:
        return string("NoAvailableKeyBinded");
    case KeyLocked:
        return string("KeyLocked");
    case KeyNotRegistered:
        return string("KeyNotRegistered");
    case HardwareUnBindedPleaseLogin:
        return string("HardwareUnBindedPleaseLogin");
    default:
        return string("invalid error code");
    }
    return string("invalid error code"); 
}

void RegClient::Unintialize()
{
    Logoff();
}
bool RegClient::Initialize( const string ip, unsigned short port )
{
    server_ip = ip;
    server_port = port;
    m_thread.start(*this);
    return true;
}

int RegClient::HeartBeat()
{
    cout<<"start to do heart beat"<<endl;
    int nRet = Success;
    ServerCommand sc = {0};
    ServerResponse sr = {0};
    if(!m_client.GetHeartBeatData(&sc, sizeof(ServerCommand)))
    {
        FLogger.information("get heart beat data failed...");
        return Fail;
    }

    try
    {
        CryptoSocket cs(server_ip, server_port);

        if (cs.Send(&sc, SC_LEN) == -1)
        {
            return SocketError;
        }

        if(cs.Recv(&sr,SR_LEN) == -1)
        {
            return SocketError;
        }

        if (sr.cmd_type == Cmd_Heartbeat && sr.cmd_state == Success)
        {
            FLogger.information("validated");
        }
        else
        {
            cout<<"validate failed with error" << ErrorCodeToString(sr.cmd_state).c_str()<<endl;
            nRet = sr.cmd_state;
            return nRet;
        }
    }
    catch (Exception& e)
    {
        FLogger.information("exception occurred while doing heartbeat");
        FLogger.information(e.what());
        return SocketError;
    }

    m_client.UpdateRegistrationInfo(&sr);

    cout<<"heartbeat finished..."<<endl;

    return nRet;
}

int RegClient::Login(PBasicInformation pInfo)
{
    cout<<"start to do heart beat"<<endl;
    int nRet = Success;
    ServerCommand sc = {0};
    ServerResponse sr = {0};

    if (!m_client.Initialize(pInfo))
    {
        FLogger.information("invalid user input data for Login");
        return InvalidDataFormat;
    }

    if(!m_client.GetLoginData(&sc, sizeof(ServerCommand)))
    {
        FLogger.information("get login data failed...");
        return Fail;
    }

    try
    {
        CryptoSocket cs(server_ip,server_port);

        if(cs.Send(&sc, SC_LEN) == -1)
        {
            return SocketError;
        }

        if(cs.Recv(&sr,SR_LEN) == -1)
        {
            return SocketError;
        }

        if (sr.cmd_type == Cmd_Login && sr.cmd_state == Success)
        {
            FLogger.information("Login successfully");
            m_client.SetLoggedIn();
            nRet = Success;
        }
        else
        {
            cout<<"login failed with error" << ErrorCodeToString(sr.cmd_state).c_str()<<endl;
            nRet = sr.cmd_state;
            return nRet;
        }
    }
    catch (Exception& e)
    {
        FLogger.information("exception occurred while logging in");
        FLogger.information(e.what());
        return SocketError;
    }

    m_client.UpdateRegistrationInfo(&sr);

    cout<<"Login finished..."<<endl;

    return nRet;
}

int RegClient::ChangePassword(const string new_password )
{
    if (!m_client.GetLoggedIn())
    {
        FLogger.information("user didnot log in");
        return UserMustBeLoggedIn;
    }

    try
    {
	    ServerCommand sc = {0};
	    ServerResponse sr = {0};
        int nRet = m_client.GetChangePasswordData(&sc, new_password);
	    if (nRet!= Success)
	    {
            FLogger.information("get change password data failed");
            return nRet;
	    }
	    CryptoSocket cs(server_ip, server_port);
	    if(cs.Send(&sc, SC_LEN) == -1)
            return SocketError;
	    
        if(cs.Recv(&sr,SR_LEN) == -1)
            return SocketError;
	
	    if (sr.cmd_type == Cmd_ChangePassword && sr.cmd_state == Success)
	    {
	        FLogger.information("Login successfully");
            m_client.SetPassword(new_password);
	        return Success;
	    }
	    else
	    {
	        cout<<"login failed with error" << ErrorCodeToString(sr.cmd_state).c_str()<<endl;
	        return sr.cmd_state;
	    }
    }
    catch (Exception& e)
    {
    	cout << "error occurred while changing password" <<endl;
        FLogger.information("error occurred while changing password");
        FLogger.information(e.what());
        return SocketError;
    }

    return Fail;
}


void RegClient::Logoff()
{
    m_stop = true;
    m_client.SetLoggedOff();
}

int RegClient::UnBind( PBasicInformation pInfo )
{
    try
    {
        ServerCommand sc = {0};
        ServerResponse sr = {0};

        if (!m_client.Initialize(pInfo))
        {
            return InvalidDataFormat;
        }
        int nRet = m_client.GetUnBindData(&sc);
        if (nRet!= Success)
        {
            FLogger.information("GetUnBindData failed");
            return nRet;
        }

        CryptoSocket cs(server_ip, server_port);
        if(cs.Send(&sc, SC_LEN) == -1)
            return SocketError;

        if(cs.Recv(&sr,SR_LEN) == -1)
            return SocketError;

        if (sr.cmd_type == Cmd_UnBind && sr.cmd_state == Success)
        {
            FLogger.information("UnBind successfully");
            return Success;
        }
        else
        {
            cout<<"UnBind failed with error" << ErrorCodeToString(sr.cmd_state).c_str()<<endl;
            return sr.cmd_state;
        }
    }
    catch (Exception& e)
    {
        cout << "error occurred while unbinding" <<endl;
        FLogger.information("error occurred while unbinding");
        FLogger.information(e.what());
        return SocketError;
    }

    return Fail;
}

int RegClient::GetKeyInfo( PKeyInfo pKI)
{
    if (NULL == pKI)
    {
        return InvalidDataFormat;
    }

    if (!m_client.GetLoggedIn())
    {
        return UserMustBeLoggedIn;
    }

    pKI->duration = m_client.GetDuration();
    pKI->generate_time = m_client.GetGenTime();
    pKI->register_time = m_client.GetRegTime();
    pKI->client_type = m_client.GetClientType();

    return Success;
}
